#include <fstream>
#include <iostream>
#include <memory>
#include <sstream>
#include <vector>

#include "xlnt/xlnt.hpp"

#include "cli/cli.h"

#include "csv2/csv2.hpp"

#ifdef _MSC_VER
// MSVC编译器
#if _MSC_VER >= 1900
#define USE_CSV_PARSER_LIB
#endif
#elif defined(__GNUC__)
// GCC编译器
#if __GNUC__ >= 7 || ((__GNUC__ == 7) && (__GNUC_MINOR__ >= 5))
#define USE_CSV_PARSER_LIB
#endif
#else
#endif

#ifdef USE_CSV_PARSER_LIB
#include "csv-parser/csv.hpp"
#endif // USE_CSV_PARSER_LIB

void newAndWrite()
{
    // Creating a 2 dimensional vector which we will write values to
    std::vector<std::vector<std::string>> wholeWorksheet;
    // Looping through each row (100 rows as per the second argument in the for loop)
    for (int outer = 0; outer < 100; outer++)
    {
        // Creating a fresh vector for a fresh row
        std::vector<std::string> singleRow;
        // Looping through each of the columns (100 as per the second argument in the for loop) in this particular row
        for (int inner = 0; inner < 100; inner++)
        {
            // Adding a single value in each cell of the row
            std::string val = std::to_string(inner + 1);
            singleRow.push_back(val);
        }
        // Adding the single row to the 2 dimensional vector
        wholeWorksheet.push_back(singleRow);
        std::clog << "Writing to row " << outer << " in the vector " << std::endl;
    }
    // Writing to the spread sheet
    // Creating the output workbook
    std::clog << "Creating workbook" << std::endl;
    xlnt::workbook wbOut;

    {
        xlnt::worksheet ws = wbOut.create_sheet();
        ws.title("sheet 2");

        xlnt::range_reference range_ref = xlnt::range_reference(1, 1, 3, 3);
        auto range = ws.range(range_ref);
        range.fill(xlnt::fill::solid(xlnt::rgb_color(192, 192, 192)));
        auto border = xlnt::border();
        xlnt::border::border_property prop;
        prop.color(xlnt::color::black());
        prop.style(xlnt::border_style::thin);
        for (auto s : xlnt::border::all_sides())
        {
            if (s == xlnt::border_side::diagonal)
            {
                continue;
            }
            border.side(s, prop);
        }
        range.border(border);
        // ws.range(range_ref).font()

        // 写入数据到单元格
        ws.cell("A1").value(42);
        ws.cell("B1").value("Hello, World!");
        auto ref = xlnt::cell_reference(3, 4);
        auto cell = ws.cell(ref);
        cell.value("单元格(row4, col3)");
        cell.fill(xlnt::fill::solid(xlnt::color::yellow()));

        auto font = xlnt::font()      //
                        .name("黑体") // SimHei
                        .size(12)     //
                        .bold(true)   //
                        .color(xlnt::color::red());
        cell.font(font);
    }

    // Setting the destination output file name
    std::string dest_filename = "output.xlsx";
    // Creating the output worksheet
    xlnt::worksheet wsOut = wbOut.active_sheet();
    // Giving the output worksheet a title/name
    wsOut.title("data");
    // We will now be looping through the 2 dimensional vector which we created above
    // In this case we have two iterators one for the outer loop (row) and one for the inner loop (column)
    std::clog << "Looping through vector and writing to spread sheet" << std::endl;
    for (int fOut = 0; fOut < wholeWorksheet.size(); fOut++)
    {
        // std::clog << "Row" << fOut << std::endl;
        for (int fIn = 0; fIn < wholeWorksheet.at(fOut).size(); fIn++)
        {
            // Take notice of the difference between accessing the vector and accessing the work sheet
            // As you may already know Excel spread sheets start at row 1 and column 1 (not row 0 and column 0 like you
            // would expect from a C++ vector) In short the xlnt cell reference starts at column 1 row 1 (hence the + 1s
            // below) and the vector reference starts at row 0 and column 0
            wsOut.cell(xlnt::cell_reference(fIn + 1, fOut + 1)).value(wholeWorksheet.at(fOut).at(fIn));
            // Further clarification to avoid confusion
            // Cell reference arguments are (column number, row number); e.g. cell_reference(fIn + 1, fOut + 1)
            // Vector arguments are (row number, column number); e.g. wholeWorksheet.at(fOut).at(fIn)
        }
    }
    std::clog << "Finished writing spread sheet" << std::endl;
    wbOut.save(dest_filename);
}

void readSheet()
{
    xlnt::workbook wb;
    wb.load("output.xlsx");
    for (auto ws : wb)
    {
        std::clog << "Sheet title : " << ws.title() << std::endl;
    }

    if (wb.contains("data"))
    {
        auto ws = wb["data"];
        ws.title();
        std::clog << "has sheet 'data' : true" << std::endl;
        // wb.remove_sheet();
    }

    auto ws = wb.active_sheet();
    std::clog << "Processing spread sheet" << std::endl;
    for (auto row : ws.rows(false))
    {
        for (auto cell : row)
        {
            std::clog << cell.to_string() << std::endl;
        }
        break;
    }
    std::clog << "Processing complete" << std::endl;
}

void storageSheetToVec()
{
    xlnt::workbook wb;
    wb.load("output.xlsx");
    auto ws = wb.active_sheet();
    std::clog << "Processing spread sheet" << std::endl;
    std::clog << "Creating a single vector which stores the whole spread sheet" << std::endl;
    std::vector<std::vector<std::string>> theWholeSpreadSheet;
    for (auto row : ws.rows(false))
    {
        std::clog << "Creating a fresh vector for just this row in the spread sheet" << std::endl;
        std::vector<std::string> aSingleRow;
        for (auto cell : row)
        {
            std::clog << "Adding this cell to the row" << std::endl;
            aSingleRow.push_back(cell.to_string());
        }
        std::clog << "Adding this entire row to the vector which stores the whole spread sheet" << std::endl;
        theWholeSpreadSheet.push_back(aSingleRow);
    }
    std::clog << "Processing complete" << std::endl;
    std::clog << "Reading the vector and printing output to the screen" << std::endl;
    for (int rowInt = 0; rowInt < theWholeSpreadSheet.size(); rowInt++)
    {
        for (int colInt = 0; colInt < theWholeSpreadSheet.at(rowInt).size(); colInt++)
        {
            std::cout << theWholeSpreadSheet.at(rowInt).at(colInt) << std::endl;
        }
    }
}

#ifdef USE_CSV_PARSER_LIB
void vincentlaucsb_csv();
#endif // USE_CSV_PARSER_LIB

void csvtest()
{
    std::clog << "********************* csv2 library *************************" << std::endl;
    {
        std::ofstream stream("foo.csv");
        csv2::Writer<csv2::delimiter<','>> writer(stream);

        std::vector<std::vector<std::string>> data;
        for (size_t r = 0; r < 100; r++)
        {
            std::vector<std::string> row;
            for (size_t c = 0; c < 100; c++)
            {
                row.emplace_back(std::to_string(c + 1));
            }
            data.emplace_back(row);
        }
        // std::vector<std::vector<std::string>> rows = {{"a", "b", "c"}, {"1", "2", "3"}, {"4", "5", "6"}};

        writer.write_rows(data);
        stream.close();
        std::clog << "Write 'foo.csv' finished." << std::endl;
    }

    {
        csv2::Reader<csv2::delimiter<','>,             //
                     csv2::quote_character<'"'>,       // 就是""" 解释为 ""
                     csv2::first_row_is_header<false>, //
                     csv2::trim_policy::trim_whitespace>
            csv;

        //   bool mmap(string_type filename);// Use this if you'd like to mmap and read from file
        //   bool parse(string_type contents); // Use this if you have the CSV contents in std::string already

        std::vector<std::vector<int>> data;
        if (csv.mmap("foo.csv")) // csv.mmap("foo.csv") csv.parse(std::string("1,2,3\n"))
        {
            // const auto header = csv.header();

            // csv2库 行和列数有bug, 必须有回车符才能正常计数
            std::clog << "Read 'foo.csv' : (" << csv.rows() << "x" << csv.cols() << ") === \n" << std::endl;
            for (const auto row : csv)
            {
                std::vector<int> vec;
                for (const auto cell : row)
                {
                    std::string value;
                    cell.read_value(value);
                    try
                    {
                        vec.push_back(std::stoi(value));
                    }
                    catch (const std::exception &)
                    {
                    }
                    std::clog << value << ", ";
                }
                if (vec.empty())
                {
                    continue;
                }
                data.emplace_back(vec);
                std::clog << std::endl;
            }
            std::clog << std::endl;
        }

        std::clog << "'foo.csv' data : (" << data.size() << "x" << data[0].size() << ") \n" << std::endl;
    }

#ifdef USE_CSV_PARSER_LIB
    vincentlaucsb_csv();
#endif // USE_CSV_PARSER_LIB
}

#ifdef USE_CSV_PARSER_LIB
void vincentlaucsb_csv()
{
    std::clog << "********************* vincentlaucsb/csv-parser library *************************" << std::endl;

    {
        /*
        std::stringstream ss; // Can also use ofstream, etc.
        auto writer = csv::make_csv_writer(ss);
        // auto writer = make_tsv_writer(ss);               // For tab-separated files
        // DelimWriter<stringstream, '|', '"'> writer(ss);  // Your own custom format
        // set_decimal_places(2);                           // How many places after the decimal will be written for
        // floats

            writer << std::vector<string>({"A", "B", "C"}) << std::deque<string>({"I'm", "too", "tired"})
                   << std::list<string>({"to", "write", "documentation."});

            writer << std::array<string, 2>({"The quick brown", "fox", "jumps over the lazy dog"});
            writer << std::make_tuple(1, 2.0, "Three");
        */

        std::ofstream of("bar.csv"); // Can also use ofstream, etc.
        auto writer = csv::make_csv_writer(of);
        std::vector<std::vector<int>> data;
        for (size_t r = 0; r < 10; r++)
        {
            std::vector<int> vec;
            for (size_t c = 0; c < 10; c++)
            {
                vec.push_back((int)c + 1);
            }
            writer << vec;
        }
        std::clog << "Write 'bar.csv' finished." << std::endl;
    }

    {
        // csv::CSVReader reader("very_big_file.csv");
        csv::CSVFormat format;
        format
            .delimiter(',') // delimiter
            // .quote('~')      //
            // .header_row(2);  // Header is on 3rd row (zero-indexed)
            .no_header()                                       // Parse CSVs without a header row
            .quote(false)                                      // Turn off quoting
            .trim({' '})                                       // Trimming Whitespace
            .variable_columns(csv::VariableColumnPolicy::KEEP) // Handling Variable Numbers of Columns ,
                                                               // csv::VariableColumnPolicy::THROW
            ;
        if (false)
        {
            // If a CSV file does not have column names, you can specify your own:
            std::vector<std::string> col_names = {"col1", "col2"};
            format.column_names(col_names);
        }

        // custom formatting options go here

        csv::CSVReader csv("bar.csv", format);

        // std::ifstream infile("bar.csv", std::ios::binary);
        // csv::CSVReader ifstream_reader(infile, format);

        // std::stringstream my_csv;
        // csv::CSVReader sstream_reader(my_csv, format);

        std::vector<std::vector<int>> data;
        std::clog << "Read 'bar.csv' === \n" << std::endl;
        for (auto &row : csv)
        { // Input iterator
            std::vector<int> vec;
            for (auto &field : row)
            {
                // By default, get<>() produces a std::string.
                // A more efficient get<string_view>() is also available, where the resulting
                // string_view is valid as long as the parent CSVRow is alive

                // row["Total Salary"].get<double>(); Retrieving values using a column name string is a cheap, constant
                // time operation.
                if (!field.is_int())
                {
                    vec.push_back(0);
                    continue;
                }
                auto value = field.get<int>();
                vec.push_back(value);
                std::clog << value << ", ";
            }
            data.emplace_back(vec);
            std::clog << std::endl;
        }
        std::clog << std::endl;
        std::clog << "'bar.csv' data : (" << data.size() << "x" << data[0].size() << ") \n" << std::endl;
    }
}
#endif // USE_CSV_PARSER_LIB

class MyCliSession : cli::CliSession
{
  public:
    explicit MyCliSession(cli::Cli &_cli, std::ostream &_out = std::cout) : CliSession(_cli, _out, 1)
    {
        if (!_out.good())
            throw std::invalid_argument("ostream invalid");

        if (false)
        {
            // coutPtr->Register(out);
            globalScopeMenu = std::make_unique<cli::Menu>();
            globalScopeMenu->Insert(
                "help", [this](std::ostream &) { Help(); }, "This help message");
            globalScopeMenu->Insert(
                "exit", [this](std::ostream &) { Exit(); }, "Quit the session");
#ifdef CLI_HISTORY_CMD
            globalScopeMenu->Insert(
                "history", [this](std::ostream &) { ShowHistory(); }, "Show the history");
#endif
        }

        ExitAction([this](std::ostream &) noexcept { exit = true; });
    }

    virtual bool eval(const std::string &cmd)
    {
        std::vector<std::string> strs;
        cli::detail::split(strs, cmd);
        if (strs.empty())
            return false;

        history.NewCommand(cmd); // add anyway to history
        try
        {

            // global cmds check
            bool found = globalScopeMenu->ScanCmds(strs, *this);

            // root menu recursive cmds check
            if (!found)
                found = current->ScanCmds(strs, *this);

            if (found)
            {
                return true;
            }
        }
        catch (const std::exception &)
        {
            return false;
        }
        catch (...)
        {
            return false;
        }
        return false;
    }
};

int main(int argc, char *argv[])
{
    std::cout << argv[0] << "\n";
    // std::cout << "Hello, from xlint-test!\n";
    // newAndWrite();
    // readSheet();
    // storageSheetToVec();
    {
        using namespace cli;
        using namespace std;
        bool exit = false;
        auto rootMenu = make_unique<Menu>("cli");

        rootMenu->Insert(
            "add", [](std::ostream &out, int x, int y) { out << x + y << "\n"; }, "add");
        rootMenu->Insert(
            "new", [](std::ostream &) { newAndWrite(); }, "test new xlsx");
        rootMenu->Insert(
            "read", [](std::ostream &) { readSheet(); }, "test read xlsx");
        rootMenu->Insert(
            "csv", [](std::ostream &) { csvtest(); }, "test csv read write");

        Cli cli(std::move(rootMenu));
        cli.ExitAction([&exit](std::ostream &) { exit = true; });
        cli.StdExceptionHandler([](std::ostream &, const std::string &, const std::exception &) {});
        // cli.WrongCommandHandler([](std::ostream &out, const std::string &cmd) {
        //     out << "Unknown command or incorrect parameters: " << cmd << ".\n";
        // });

        std::ostringstream sout;
        // CliSession session(cli, sout);
        MyCliSession session(cli, sout);
        // session.Current(rootMenu.get());
        // session.Feed("add 1 2");
        // session.Current(nullptr);
        // session.Feed("add 1 2");

        // file session.
        // std::stringstream sin("hello\n");
        // CliFileSession input(cli, sin);
        // input.Start();

        std::string text;
        std::cout << ">>> ";
        while (!exit && std::getline(std::cin, text))
        {

            if (session.eval(text))
            {
                std::cout << sout.str() << std::endl;
                sout.clear();
                sout.str("");
            }
            std::cout << ">>> ";
        }
    }

    return 0;
}
